package com.onec.service.gateway.filter;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.onec.service.api.content.FilterContent;
import com.onec.service.api.dto.request.RequestData;
import com.onec.service.api.dto.request.gateway.BizContent;
import com.onec.service.api.enums.error.GatewayEnum;
import com.onec.service.api.exception.GatewayException;
import com.onec.service.api.utils.StringMapUtil;
import com.onec.service.gateway.service.system.BaseSourceService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;


/**
 * 自定义全局过滤器
 * APP 请求做认证和鉴权：AGENT 请求做鉴权：
 *
 * @author ONEC
 * @Component: 注解生效
 */
@Component
public class RequestEncryptFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger(RequestEncryptFilter.class);

    @Autowired
    private Map<String, BaseSourceService> serviceMap;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        exchange.getAttributes().put("startTime", System.currentTimeMillis());
        HttpHeaders httpHeaders = exchange.getRequest().getHeaders();

        log.info("网关请求：请求方法：method：【{}】:，请求地址：uri:【{}】",
                exchange.getRequest().getMethod(), exchange.getRequest().getURI());
        if (exchange.getRequest().getMethod().equals(HttpMethod.POST)) {
            //重新构造request，参考ModifyRequestBodyGatewayFilterFactory
            ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
            MediaType mediaType = httpHeaders.getContentType();
            //重点
            Mono modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
                //因为约定了终端传参的格式，所以只考虑json的情况，如果是表单传参，请自行发挥
                if (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType)
                        || Objects.equals(mediaType, MediaType.APPLICATION_JSON_VALUE)) {
                    RequestData requestData = JSONUtil.toBean(body, RequestData.class);
                    BizContent bizContent = verifySignatureJson(requestData, exchange);
                    return Mono.just(JSONUtil.toJsonStr(bizContent));
                }
                if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(mediaType)) {
                    Map<String, Object> requestMap = StringMapUtil.strTransForMap(body);
                    RequestData requestData = BeanUtil.mapToBean(requestMap, RequestData.class, true, CopyOptions.create());
                    BizContent bizContent = verifySignatureJson(requestData, exchange);

                    Map resultMap = BeanUtil.beanToMap(bizContent);
                    String requestBody = MapUtil.sortJoin(resultMap, "&", "=", true);
                    return Mono.just(requestBody);
                }
                return Mono.just(body);
            });
            BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
            HttpHeaders headers = new HttpHeaders();
            headers.putAll(exchange.getRequest().getHeaders());
            //猜测这个就是之前报400错误的元凶，之前修改了body但是没有重新写content length
            headers.remove("Content-Length");
            //MyCachedBodyOutputMessage 这个类完全就是CachedBodyOutputMessage，只不过CachedBodyOutputMessage不是公共的
            CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
            //MyCachedBodyOutputMessage outputMessage = new MyCachedBodyOutputMessage(exchange, headers);
            Mono then = bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
                ServerHttpRequest decorator = this.decorate(exchange, headers, outputMessage);
                return returnMono(chain, exchange.mutate().request(decorator).build());
            }));
            return then;
        } else {

            return returnMono(chain, exchange);
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }


    private Mono<Void> returnMono(GatewayFilterChain chain, ServerWebExchange exchange) {
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            Long startTime = exchange.getAttribute("startTime");
            if (startTime != null) {
                long executeTime = (System.currentTimeMillis() - startTime);
                log.info("网关数据耗时：{}ms,请求返回状态码:{}", executeTime,
                        Objects.requireNonNull(exchange.getResponse().getStatusCode()).value());
            }
        }));
    }

    /**
     * 做验签和加密处理：
     *
     * @param requestData
     * @param exchange
     * @return
     */
    private BizContent verifySignatureJson(RequestData requestData, ServerWebExchange exchange) {
        String source = exchange.getRequest().getHeaders().getFirst(FilterContent.AUTHORIZE_SOURCE);
        log.info("密文:{}", requestData);
        String agentNo = exchange.getRequest().getHeaders().getFirst(FilterContent.AUTHORIZE_AGENT_NO);
        requestData.setAgentNo(agentNo);

        String sign = exchange.getRequest().getHeaders().getFirst(FilterContent.AUTHORIZE_SIGN);
        if (Optional.ofNullable(sign).isEmpty()) {
            throw new GatewayException(GatewayEnum.NO_SIGN);
        }
        requestData.setSign(sign);

        BizContent bizContent = serviceMap.get(source).decryptRequestData(requestData, exchange);
        bizContent.setAgentNo(agentNo);
        return bizContent;
    }


    ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, CachedBodyOutputMessage outputMessage) {
        return new ServerHttpRequestDecorator(exchange.getRequest()) {

            @Override
            public HttpHeaders getHeaders() {
                long contentLength = headers.getContentLength();
                HttpHeaders httpHeaders = new HttpHeaders();
                httpHeaders.putAll(super.getHeaders());
                if (contentLength > 0L) {
                    httpHeaders.setContentLength(contentLength);
                } else {
                    httpHeaders.set("Transfer-Encoding", "chunked");
                }
                return httpHeaders;
            }

            @Override
            public Flux<DataBuffer> getBody() {
                return outputMessage.getBody();
            }
        };
    }
}
